package com.wf.netty.coder.inner;

import com.wf.netty.coder.string.StringProcessHandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;

/**
 * 通用内置解码器
 *
 * @author wf
 * @since 2022/5/26
 */
@Slf4j
public class NettyInnerDecoderTest {
    private static final SecureRandom RANDOM = new SecureRandom();

    static String spliter = "\r\n";
    static String spliter2 = "\t";
    static String content = "test123";
    /**
     * 版本
     */
    static final Integer VERSION = 100;

    /**
     * 魔数
     */
    static final Integer MAGIC_NUM = 233;


    /**
     * 判断在二进制字节流中是否存在换行符"\n"或者"\r\n"的字节码。如果有，就以此位置为结束位置，把
     * 从可读索引到结束位置之间的字节作为解码成功后的ByteBuf数据包。
     * LineBasedFrameDecoder支持配置一个最大长度值，表示解码出来的ByteBuf能包含的最大字节数。
     * 如果连续读取到最大长度后仍然没有发现换行符，就会抛出异常。
     */
    @Test
    public void testLineBasedFrameDecoder() {
        ChannelInitializer<EmbeddedChannel> initializer = new ChannelInitializer<EmbeddedChannel>() {
            @Override
            protected void initChannel(EmbeddedChannel ch) throws Exception {
                ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                ch.pipeline().addLast(new StringDecoder());
                ch.pipeline().addLast(new StringProcessHandler());
            }
        };
        EmbeddedChannel channel = new EmbeddedChannel(initializer);
        byte[] bytes = content.getBytes(StandardCharsets.UTF_8);

        for (int i = 0; i < 10; i++) {
            int len = RANDOM.nextInt(3) + 1;
            ByteBuf buffer = Unpooled.buffer();
            for (int j = 0; j < len; j++) {
                buffer.writeBytes(bytes);
            }
            // 发送"\r\n"回车换行符作为包结束符
            buffer.writeBytes(spliter.getBytes(StandardCharsets.UTF_8));
            channel.writeInbound(buffer);
        }
    }

    /**
     * DelimiterBasedFrameDecoder解码器不仅可以使用换行符，还可以使用其他特殊字符作为数据包的分隔符，例如制表符"\t"
     */
    @Test
    public void testDelimiterBasedFrameDecoder() {
        ByteBuf delimiter =
                Unpooled.copiedBuffer(spliter2.getBytes(StandardCharsets.UTF_8));
        ChannelInitializer initializer
                = new ChannelInitializer<EmbeddedChannel>() {
            protected void initChannel(EmbeddedChannel ch) {
                ch.pipeline().addLast(
                        new DelimiterBasedFrameDecoder(1024, true, delimiter));
                ch.pipeline().addLast(new StringDecoder());
                ch.pipeline().addLast(new StringProcessHandler());
            }
        };

        EmbeddedChannel channel = new EmbeddedChannel(initializer);
        byte[] bytes = content.getBytes(StandardCharsets.UTF_8);

        for (int i = 0; i < 10; i++) {
            int len = RANDOM.nextInt(3) + 1;
            ByteBuf buffer = Unpooled.buffer();
            for (int j = 0; j < len; j++) {
                buffer.writeBytes(bytes);
            }
            // 发送"\r\n"回车换行符作为包结束符
            buffer.writeBytes(spliter2.getBytes(StandardCharsets.UTF_8));
            channel.writeInbound(buffer);
        }
    }

    /**
     * LengthFieldBasedFrameDecoder,参数设置要结合实际传输的内容结构，哪些是长度，哪些是那版本、魔数、其它内容等，哪些是真正的content，
     * 需要结合这些内容所占的字节数进行动态调整
     *
     * 参数说明：
     * int maxFrameLength,       //发送的数据包的最大长度
     * int lengthFieldOffset,    //长度字段偏移量
     * int lengthFieldLength,    //长度字段本身占用的字节数
     * int lengthAdjustment,     //长度字段的偏移量矫正
     * int initialBytesToStrip)  //丢弃的起始字节数
     */
    @Test
    public void testLengthFieldBasedFrameDecoder1() {
        LengthFieldBasedFrameDecoder frameDecoder = new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4);
        EmbeddedChannel channel = getEmbeddedChannel(frameDecoder);
        for (int i = 0; i < 10; i++) {
            ByteBuf buffer = Unpooled.buffer();
            String s = i + "次发送->" + content;
            byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
            buffer.writeInt(bytes.length);
            buffer.writeBytes(bytes);
            channel.writeInbound(buffer);
        }
    }

    @Test
    public void testLengthFieldBasedFrameDecoder2() {
        // 长度4个字节，版本2个字节，真实内容丢弃前六个字节
        LengthFieldBasedFrameDecoder decoder = new LengthFieldBasedFrameDecoder(1024, 0, 4, 2, 6);
        EmbeddedChannel channel = getEmbeddedChannel(decoder);
        for (int i = 0; i < 10; i++) {
            ByteBuf buffer = Unpooled.buffer();
            String s = i + "次发送->" + content;
            byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
            // 字段长度4字节
            buffer.writeInt(bytes.length);
            // 版本2字节
            buffer.writeChar(VERSION);
            // 实际内容
            buffer.writeBytes(bytes);
            channel.writeInbound(buffer);
        }
    }

    @Test
    public void testLengthFieldBasedFrameDecoder3() {
        LengthFieldBasedFrameDecoder decoder = new LengthFieldBasedFrameDecoder(1024, 2, 4, 4, 10);
        EmbeddedChannel channel = getEmbeddedChannel(decoder);
        for (int i = 0; i < 10; i++) {
            ByteBuf buffer = Unpooled.buffer();
            String s = i + "次发送->" + content;
            byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
            // 版本2字节
            buffer.writeChar(VERSION);
            // 字段长度4字节
            buffer.writeInt(bytes.length);
            // 魔数4字节
            buffer.writeInt(MAGIC_NUM);
            // 实际内容
            buffer.writeBytes(bytes);
            channel.writeInbound(buffer);
        }

    }

    private EmbeddedChannel getEmbeddedChannel(LengthFieldBasedFrameDecoder decoder) {
        ChannelInitializer<EmbeddedChannel> initializer = new ChannelInitializer<EmbeddedChannel>() {
            @Override
            protected void initChannel(EmbeddedChannel ch) throws Exception {
                ch.pipeline().addLast(decoder);
                ch.pipeline().addLast(new StringDecoder(StandardCharsets.UTF_8));
                ch.pipeline().addLast(new StringProcessHandler());
            }
        };
        return new EmbeddedChannel(initializer);
    }
}
